using System;
using System.Collections;
using System.Data;

using FreeFlow;
using System.Runtime.Serialization;
using System.Diagnostics;
using System.IO;

namespace FreeFlow.Administration
{
	/// <summary>
	/// A process folder.
	/// </summary>
	public class Folder : DatabaseAware, IDeletable, IRefreshable, IUpdatable
	{
    internal Folder(Server server, IDataReader reader) : base(server)
    {
      LoadFromReader(reader);
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="Folder"/> class for the specified folder ID.
    /// </summary>
    /// <param name="server">The server.</param>
    /// <param name="folderId">The folder id.</param>
    public Folder(Server server, string folderId) : base(server)
    {
      if (string.IsNullOrEmpty(folderId))
        throw new ArgumentNullException("folderId", "folderId must be supplied");

      LoadForFolder(folderId);
    }

    private void LoadForFolder(string folderId)
    {
      string tableName;
      using (IDataReader reader = Server.ExecuteReader(
        string.Format("SELECT eTableName FROM eMap WHERE eMapName IN (SELECT eMapName FROM eFolder WHERE eFolderID = '{0}')", folderId)))
      {
        if (reader.Read()) 
          tableName = reader["eTableName"].ToString();
        else
          throw new FreeFlowException(string.Format("Unable to create Folder instance for folder ID '{0}', can not locate eMap.eTableName for folder", folderId));
      }

      using (IDataReader reader = Server.ExecuteReader(
        string.Format(
        "SELECT * FROM eFolder LEFT JOIN {0} ON eFolder.eFolderID = {0}.EFOLDERID WHERE eFolder.eFolderID = '{1}'", tableName, folderId)))
      {
        if (reader.Read())
          LoadFromReader(reader);
        else
        {
          // check if there is an entry in the eFolder table
          int folderCount = Convert.ToInt32(Server.ExecuteScalar(
            string.Format("SELECT COUNT(*) FROM eFolder WHERE eFolderID='{1}'", folderId)));
          if (folderCount == 0)
            throw new FreeFlowException(string.Format("Unable to create Folder instance for folder ID '{0}', record not found in eFolder", folderId));

          throw new FreeFlowException(string.Format("Unable to create Folder instance for folder ID '{0}'", folderId));
        }
      }
    }

    private FolderVariableCollection customVariables;
    /// <summary>
    /// Gets the custom variables for this folder.
    /// </summary>
    public FolderVariableCollection CustomVariables
    {
      get
      {
        return customVariables;
      }
    }

    private void LoadFromReader(IDataReader reader)
    {
      if (reader != null)
      {
        name = reader["eFolderName"].ToString();
        folderId = reader["eFolderID"].ToString();
        subject = reader["eSubject"].ToString();
        originator = reader["eOriginator"].ToString();
        priority = Convert.ToInt32(reader["ePriority"]);
        created = (DateTime)reader["eCreationTime"];
        if (reader["eUpdated"] != System.DBNull.Value)
          updated = (DateTime)reader["eUpdated"];
        else 
          updated = Constants.NullDateTime;
        if (reader["eEntryTime"] != System.DBNull.Value)
          entryTime = (DateTime)reader["eEntryTime"];
        else 
          entryTime = Constants.NullDateTime;
        deadline = (DateTime)reader["eDeadline"];
        stageName = reader["eStageName"].ToString();
        parentFolderId = reader["eParent"].ToString();
        mapName = reader["eMapName"].ToString();
        category = reader["eCategory"].ToString();
        lockedBy = reader["eActionUser"].ToString();
        lockedAction = reader["eActionName"].ToString();
        if (reader["eActionTime"] != System.DBNull.Value)
          lockedSince = (DateTime)reader["eActionTime"];
        else
          lockedSince = Constants.NullDateTime;
        isArchived = Convert.ToBoolean(reader["eArchived"]);

        // custom variable values
        customVariables = new FolderVariableCollection(reader);
      }
		}

    private string name;
    /// <summary>
    /// Gets or sets the name of the folder.
    /// </summary>
    public string Name
    {
      get
      {
        return name;
      }
      set
      {
        name = value;
      }
    }

    private string folderId;
    /// <summary>
    /// Gets the folder ID.
    /// </summary>
    public string FolderId
    {
      get
      {
        return folderId;
      }
    }

    private string subject;
    /// <summary>
    /// Gets or sets the folder's subject.
    /// </summary>
    public string Subject
    {
      get
      {
        return subject;
      }
      set
      {
        subject = value;
      }
    }

    private string originator;
    /// <summary>
    /// Gets the originator of the folder.
    /// </summary>
    public string Originator
    {
      get
      {
        return originator;
      }
    }

    private int priority;
    /// <summary>
    /// Gets or sets the priority of the folder.
    /// </summary>
    public int Priority
    {
      get
      {
        return priority;
      }
      set
      {
        priority = value;
      }
    }

    private DateTime created;
    /// <summary>
    /// Gets the date and time when the folder was created.
    /// </summary>
    public DateTime Created
    {
      get
      {
        return created;
      }
    }

    private DateTime updated;
    /// <summary>
    /// Gets the date and time the folder was last updated.
    /// </summary>
    public DateTime Updated
    {
      get
      {
        return updated;
      }
    }

    private DateTime entryTime;
    /// <summary>
    /// Gets the date and time the folder entered its current stage.
    /// </summary>
    public DateTime EntryTime
    {
      get
      {
        return entryTime;
      }
    }

    private DateTime deadline;
    /// <summary>
    /// Gets the deadline for this folder.
    /// </summary>
    public DateTime Deadline
    {
      get
      {
        return deadline;
      }
    }

    private bool isArchived;
    /// <summary>
    /// Gets a value indicating whether this folder has been archived.
    /// </summary>
    /// <value>
    /// 	<c>true</c> if this folder has been archived; otherwise, <c>false</c>.
    /// </value>
    public bool IsArchived
    {
      get
      {
        return isArchived;
      }
    }

    private string stageName;
    /// <summary>
    /// Gets the name of the stage that this folder is currently at.
    /// </summary>
    public string StageName
    {
      get
      {
        return stageName;
      }
    }

    private string category;
    /// <summary>
    /// Gets or sets the category of this folder.
    /// </summary>
    public string Category
    {
      get
      {
        return category;
      }
      set
      {
        category = value;
      }
    }

    private string parentFolderId;
    /// <summary>
    /// Gets the ID of this folder's parent folder.
    /// </summary>
    public string ParentFolderId
    {
      get
      {
        return parentFolderId;
      }
    }

    private Folder parent;
    /// <summary>
    /// Gets the parent folder for this folder.
    /// </summary>
    public Folder GetParent()
    {
      if (parent == null)
        parent = Server.GetFolder(parentFolderId);
      return parent;
    }
 
    private History history;
    /// <summary>
    /// Gets the history of this folder.
    /// </summary>
    public History History
    {
      get
      {
        if (history == null)
          history = new History(Server, folderId);
        return history;
      }
    }

    private FolderNoteCollection notes;
    /// <summary>
    /// Gets the notes that have been added to this folder.
    /// </summary>
    public FolderNoteCollection Notes
    {
      get
      {
        if (notes == null)
          notes = new FolderNoteCollection(Server, folderId);
        return notes;
      }
    }

    private string alertMessage;
    /// <summary>
    /// Gets the alert message for this folder.
    /// </summary>
    public string AlertMessage
    {
      get
      {
        if (alertMessage == null)
        {
          string sql = string.Format(
            "SELECT TOP 1 eAlertMessage FROM eAlert WHERE eFolderID = '{0}' ORDER BY eUpdated DESC", folderId);
          if (Server.DatabaseType == DatabaseType.Oracle)
            sql = string.Format(
            "SELECT eAlertMessage FROM eAlert WHERE eFolderID = '{0}' AND ROWNUM <= 1 ORDER BY eUpdated DESC", folderId);
          
          using (IDataReader reader = Server.ExecuteReader(sql))
          {
            if (reader.Read())
            {
              alertMessage = reader[0].ToString();
            }
            else
              alertMessage = string.Empty;
          }
        }
        return alertMessage;
      }
    }

    private AlertCollection toDoList;
    /// <summary>
    /// Gets the To Do list alerts for this folder.
    /// </summary>
    public AlertCollection ToDoList
    {
      get
      {
        if (toDoList == null)
          toDoList = new AlertCollection(Server, folderId, AlertType.ToDo);
        return toDoList;
      }
    }

    private AlertCollection watchList;
    /// <summary>
    /// Gets the Watch list alerts for this folder.
    /// </summary>
    public AlertCollection WatchList
    {
      get
      {
        if (watchList == null)
          watchList = new AlertCollection(Server, folderId, AlertType.Watch);
        return watchList;
      }
    }

    private AlertCollection deletionList;
    /// <summary>
    /// Gets the deletion alerts for this folder.
    /// </summary>
    public AlertCollection DeletionList
    {
      get
      {
        if (deletionList == null)
          deletionList = new AlertCollection(Server, folderId, AlertType.Deletion);
        return deletionList;
      }
    }

    private string mapName;
    /// <summary>
    /// Gets the name of the map that this folder was created in.
    /// </summary>
    public string MapName
    {
      get
      {
        return mapName;
      }
    }

    /// <summary>
    /// Deletes this folder from the database.
    /// </summary>
    public void Delete()
    {
      Map map = new Map(Server, mapName);

      // delete
      using (IDbTransaction transaction = Server.BeginTransaction())
      {
        Server.ExecuteNonQuery(transaction, 
          string.Format("DELETE FROM {0} WHERE eFolderID = '{1}'", map.TableName, folderId));
        Server.ExecuteNonQuery(transaction, 
          string.Format("DELETE FROM eFolder WHERE eFolderID = '{0}'", folderId));
        Server.ExecuteNonQuery(transaction, 
          string.Format("DELETE FROM eWait WHERE eFolderID = '{0}'", folderId));
        Server.ExecuteNonQuery(transaction, 
          string.Format("DELETE FROM eEvent WHERE eFolderID = '{0}'", folderId));
        Server.ExecuteNonQuery(transaction, 
          string.Format("DELETE FROM eAssignment WHERE eFolderID = '{0}'", folderId));
        Server.ExecuteNonQuery(transaction, 
          string.Format("DELETE FROM eAttachment WHERE eKey LIKE '0\t{0}\t%'", folderId));
        Server.ExecuteNonQuery(transaction,
          string.Format("UPDATE eAlert SET eAlertTime = " + Server.GetDate + ", " +
          "eAlertMessage = 'Deleted by Administrator', eAlertType = '~' WHERE eFolderID = '{0}'", folderId));

        transaction.Commit();
      }
    }

    private AttachmentCollection attachments;
    /// <summary>
    /// Gets the attachments for this folder.
    /// </summary>
    public AttachmentCollection Attachments
    {
      get
      {
        if (attachments == null)
          attachments = new AttachmentCollection(Server, AttachmentType.Folder, folderId);
        return attachments;
      }
    }

    private FolderCollection childFolders;
    /// <summary>
    /// Gets the child folders for this folder.
    /// </summary>
    public FolderCollection ChildFolders
    {
      get
      {
        if (childFolders == null)
          childFolders = new FolderCollection(Server, folderId);
        return childFolders;
      }
    }

    private DesignerLog designerLog;
    /// <summary>
    /// Gets the designer log entries for this folder.
    /// </summary>
    public DesignerLog DesignerLog
    {
      get
      {
        if (designerLog == null)
          designerLog = new DesignerLog(Server, folderId);
        return designerLog;
      }
    }

    #region lock details

    private string lockedBy;
    /// <summary>
    /// Gets the name of the user who has locked this folder.
    /// </summary>
    public string LockedBy
    {
      get
      {
        return lockedBy;
      }
    }

    private string lockedAction;
    /// <summary>
    /// Gets the name of the locked action.
    /// </summary>
    public string LockedAction
    {
      get
      {
        return lockedAction;
      }
    }

    private DateTime lockedSince;
    /// <summary>
    /// Gets the date when the folder was locked.
    /// </summary>
    public DateTime LockedSince
    {
      get
      {
        return lockedSince;
      }
    }

    /// <summary>
    /// Unlocks this folder if it is in use by a user.
    /// </summary>
    public void Unlock()
    {
      Server.ExecuteNonQuery(string.Format(
        "UPDATE eFolder SET eActionName = '', eActionUser = '', eActionTime = NULL WHERE eFolderID = '{0}'", folderId));
    }

    /// <summary>
    /// Gets a value indicating whether this folder is locked.
    /// </summary>
    /// <value><c>true</c> if this folder is locked; otherwise, <c>false</c>.</value>
    public bool IsLocked
    {
      get
      {
        return !string.IsNullOrEmpty(lockedBy) || !string.IsNullOrEmpty(lockedAction) || (lockedSince != Constants.NullDateTime);
      }
    }

    #endregion

    private FlagCollection waitingForFlags;
    /// <summary>
    /// Gets the flags this folder is waiting for.
    /// </summary>
    public FlagCollection WaitingForFlags
    {
      get
      {
        if (waitingForFlags == null)
          waitingForFlags = new FlagCollection(Server, folderId);
        return waitingForFlags;
      }
    }

    private TimerCollection waitingForTimers;
    /// <summary>
    /// Gets the timers this folder is waiting for.
    /// </summary>
    public TimerCollection WaitingForTimers
    {
      get
      {
        if (waitingForTimers == null)
          waitingForTimers = new TimerCollection(Server, folderId);
        return waitingForTimers;
      }
    }

    #region IRefreshable Members

    /// <summary>
    /// Refreshes the data for this folder.
    /// </summary>
    public void Refresh()
    {
      parent = null;
      history = null;
      notes = null;
      alertMessage = null;
      toDoList = null;
      watchList = null;
      attachments = null;
      childFolders = null;
      designerLog = null;
      waitingForFlags = null;
      waitingForTimers = null;
      LoadForFolder(folderId);
    }

    #endregion
    #region IUpdatable Members

    /// <summary>
    /// Applies the changes made to this folder.
    /// </summary>
    public void ApplyChanges()
    {
      using (IDbTransaction transaction = Server.BeginTransaction())
      {
        Server.ExecuteNonQuery(transaction, string.Format(
          "UPDATE eFolder SET eSubject = '{0}', eCategory = '{1}', ePriority = {2}, eFolderName='{3}' where eFolderID = '{4}'",
          SqlEscape(subject), SqlEscape(category), priority, SqlEscape(name), folderId));
        Server.ExecuteNonQuery(transaction, string.Format(
          "UPDATE eAlert SET eSubject = '{0}', eCategory = '{1}', ePriority = {2}, eFolderName='{3}' where eFolderID = '{4}'",
          SqlEscape(subject), SqlEscape(category), priority, SqlEscape(name), folderId));

        transaction.Commit();
      }
    }

    #endregion

    /// <summary>
    /// Determines whether the folder has passed through the specified stage.
    /// </summary>
    /// <param name="stageName">Name of the stage.</param>
    /// <returns>
    /// 	<c>true</c> if the folder has passed through the specified stage; otherwise, <c>false</c>.
    /// </returns>
    public bool HasPassedThroughStage(string stageName)
    {
      for (int i = 0; i < History.Count; i++)
      {
        if (History[i].FromStage == stageName)
          return true;
        if (History[i].ToStage == stageName)
          return true;
      }
      return false;
    }

    /// <summary>
    /// Determines whether the folder has passed through specfied action.
    /// </summary>
    /// <param name="stageName">Name of the stage.</param>
    /// <param name="actionName">Name of the action.</param>
    /// <returns>
    /// 	<c>true</c> if the folder has passed through specfied action; otherwise, <c>false</c>.
    /// </returns>
    public bool HasPassedThroughAction(string stageName, string actionName)
    {
      for (int i = 0; i < History.Count; i++)
      {
        if ((History[i].FromStage == stageName) && (History[i].Action == actionName))
          return true;
      }
      return false;
    }

    /// <summary>
    /// Opens this folder in the web client.
    /// </summary>
    /// <param name="webLocation">The location of the web client e.g. http://localhost/Metastorm</param>
    public void OpenInClient(string webLocation)
    {
      Process.Start(webLocation +
        "/eFolder.aspx?Client=External&ResizeForm=false&FolderID=" + folderId);
    }

    /// <summary>
    /// Adds an attachment to this folder.
    /// </summary>
    /// <param name="fileName">Name of the file.</param>
    /// <param name="attachmentName">The name that will be given to the attachment in the database.</param>
    public void AddAttachment(string fileName, string attachmentName)
    {
      using (FileStream stream = new FileStream(fileName, FileMode.Open))
      {
        byte[] data = new byte[stream.Length];
        stream.Read(data, 0, (int)stream.Length);
        Server.SetAttachment(AttachmentType.Folder, attachmentName, folderId, data);
      }
    }
  }
}
